package demo.db.mybatis.plugin;

import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import demo.spring.framework.jdbc.datasource.lookup.RoutingDataSourceDemo;

/**
 * 读写分离插件
 * 
 * @author junying.han
 *
 */
@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }) })
public class DynamicTransactionManagerPlugin implements Interceptor {

    public DynamicTransactionManagerPlugin() {
        System.err.println("init DynamicTransactionManagerPlugin");
    }

    private static final Logger log = LoggerFactory.getLogger(DynamicTransactionManagerPlugin.class);

    private static final String REGEX = ".*insert\\u0020.*|.*delete\\u0020.*|.*update\\u0020.*";

    private static final Map<String, String> cacheMap = new ConcurrentHashMap<>();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        log.info("DynamicTransactionManagerPlugin.intercept");
        boolean sysnchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        if (!sysnchronizationActive) {
            Object[] objects = invocation.getArgs();
            MappedStatement mappedStatement = (MappedStatement) objects[0];

            String dataSourceType = null;

            if ((dataSourceType = cacheMap.get(mappedStatement.getId())) == null) {
                // 读方法
                log.info("DynamicTransactionManagerPlugin.intercept.ms.getSqlCommandType()|"
                        + mappedStatement.getSqlCommandType());
                if (mappedStatement.getSqlCommandType().equals(SqlCommandType.SELECT)) {
                    // !selectKey 为自增id查询主键(SELECT LAST_INSERT_ID() )方法，使用主库
                    if (mappedStatement.getId().contains(SelectKeyGenerator.SELECT_KEY_SUFFIX)) {
                        dataSourceType = "slave";
                    } else {
                        BoundSql boundSql = mappedStatement.getSqlSource().getBoundSql(objects[1]);
                        String sql = boundSql.getSql().toLowerCase(Locale.CHINA).replaceAll("[\\t\\n\\r]", " ");
                        log.info("DynamicTransactionManagerPlugin.intercept.sql|" + sql);
                        if (sql.matches(REGEX)) {
                            dataSourceType = "master";
                        } else {
                            dataSourceType = "slave";
                        }
                    }
                } else {
                    dataSourceType = "master";
                }
                //
                log.debug("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..", mappedStatement.getId(), dataSourceType);
                log.debug("设置方法[{}] use [{}] Strategy, SqlCommandType [{}]..",
                        mappedStatement.getSqlCommandType().name());
                cacheMap.put(mappedStatement.getId(), dataSourceType);
            }
            RoutingDataSourceDemo.setDataSource(dataSourceType);
        }
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        log.info("DynamicTransactionManagerPlugin.plugin");
        if (target instanceof Executor) {
            return Plugin.wrap(target, DynamicTransactionManagerPlugin.this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {
        log.info("DynamicTransactionManagerPlugin.setProperties");
    }
}
